#!/usr/bin/env python
# -*- coding:utf8 -*-

import re
import subprocess

from .singleton import Singleton


class MonitorSystem(metaclass=Singleton):
    def __init__(self, **kwargs):
        self.top_times = 2
        self.m_cpu = {}
        self.m_mem = {}
        self.m_load_average = {}
        self.m_swap = {}
        self.top_info = ""
        self.state = None
    CPU_MAX_OVERLOAD = 80
    MEM_MAX_OVERLOAD = 0.8
    SLAVE_STATE_GREEN = 1
    SLAVE_STATE_RED = 0

    def top(self):
        try:
            tmp_info = subprocess.Popen(["top", "-bi", "-n", "%s" % self.top_times], stdout=subprocess.PIPE)
            out, err = tmp_info.communicate()
            self.top_info = out.decode('unicode-escape')
        except Exception as e:
            self.top_info = str(e)

    def _pack_status(self):
        """
        pack attr as status dict
        :return: status:dict
        """
        return {'cpu': self.m_cpu, 'mem': self.m_mem, 'swap': self.m_swap}

    def get_state(self, black=False):
        sys_info = {}
        try:
            if not self.top_info or black:
                self.top()
            filtered_info = re.findall(r'(top.*?)(Tasks.*?)(Cpu.*?)(KiB Mem.*?)(KiB Swap.*?)\n',
                                       self.top_info, re.S)[self.top_times - 1]
            _top = filtered_info[0]
            _task = filtered_info[1]
            _cpu = filtered_info[2]
            _mem = filtered_info[3].replace('+', '0 ')
            _swap = filtered_info[4].replace('+', '0 ')
            self._parse_load_average(_top)
            self._parse_cpu_info(_cpu)
            self._parse_mem_info(_mem)
            self._parse_swap_info(_swap)
            self.check_hw_state()
            sys_info = self._pack_status()
        except Exception as e:
            print(f'parseTop failed, {e}')
        finally:
            return self.state, sys_info


    def _parse_load_average(self, top_info_str):
        load_average_list = self.extract('load average:(.*?),[\s+](.*?),[\s+](.*?)\n', top_info_str)
        self.m_load_average['1min'] = load_average_list[0]
        self.m_load_average['5min'] = load_average_list[1]
        self.m_load_average['15min'] = load_average_list[2]

    def _parse_cpu_info(self, cpu_info_str):
        self.m_cpu['us'] = self.extract(r'(\d+.\d+)[\s+]us', cpu_info_str)
        self.m_cpu['sy'] = self.extract(r'(\d+.\d+)[\s+]sy', cpu_info_str)
        self.m_cpu['ni'] = self.extract(r'(\d+.\d+)[\s+]ni', cpu_info_str)
        self.m_cpu['id'] = self.extract(r'(\d+.\d+)[\s+]id', cpu_info_str)
        self.m_cpu['wa'] = self.extract(r'(\d+.\d+)[\s+]wa', cpu_info_str)
        self.m_cpu['hi'] = self.extract(r'(\d+.\d+)[\s+]hi', cpu_info_str)
        self.m_cpu['si'] = self.extract(r'(\d+.\d+)[\s+]si', cpu_info_str)
        self.m_cpu['st'] = self.extract(r'(\d+.\d+)[\s+]st', cpu_info_str)

    def _parse_mem_info(self, mem_info_str):
        self.m_mem['total'] = self.extract(r'(\d+)[\s+]total', mem_info_str)
        self.m_mem['used'] = self.extract(r'(\d+)[\s+]used', mem_info_str)
        self.m_mem['free'] = self.extract(r'(\d+)[\s+]free', mem_info_str)
        self.m_mem['buffers'] = self.extract(r'(\d+)[\s+]buff.*?', mem_info_str)

    def _parse_swap_info(self, swap_info_str):
        self.m_swap['total'] = self.extract(r'(\d+)[\s+]total.*?', swap_info_str)
        self.m_swap['used'] = self.extract(r'(\d+)[\s+]used.*?', swap_info_str)
        self.m_swap['free'] = self.extract(r'(\d+)[\s+]free.*?', swap_info_str)
        self.m_swap['cached'] = self.extract(r'(\d+)[\s+][cached|avail].*?', swap_info_str)

    def mk_result(self, *args):
        """
        merge all args to a dict
        :param args: all args should be dict
        :return: a dict include all arg
        """
        result = {}
        for it in args:
            if type(it) is not dict:
                raise Exception('{} should be a dict'.format(it))
            result.update(it)
        return result

    def extract(self, regex, text):
        ret = re.findall(regex, text)
        return ret[0] if ret else -1

    def get_cpu_overload(self):
        """
        return used cpu
        """
        return 100.0 - float(self.m_cpu['id'])

    def get_mem_overload(self):
        """
        return used memory
        """
        return int(self.m_mem['used'])

    def get_total_mem(self):
        return int(self.m_mem['total'])

    def get_hardware_overload(self):
        return self.get_cpu_overload(), self.get_mem_overload()

    def get_load_average(self, minute=1):
        """
        get load average by specify minute
        :param minute: minute in [1, 5, 15, -1]
        :return: load average for nearly 1 minute if minute == 1;
                load average for nearly 5 minutes if minute == 5;
                load average for nearly 15 minutes if minute == 15;
                a tuple include load average for nearly 1, 5, 15minutes if minute == -1
        """
        if minute not in (1, 5, 15, -1):
            minute = -1

        if not self.m_load_average:
            self.get_state()

        if minute == 1:
            return float(self.m_load_average['1min'])
        elif minute == 5:
            return float(self.m_load_average['5min'])
        elif minute == 15:
            return float(self.m_load_average['15min'])
        else:
            one_min = self.m_load_average['1min']
            five_min = self.m_load_average['5min']
            fifteen_min = self.m_load_average['15min']
            return float(one_min), float(five_min), float(fifteen_min)

    def get_logic_cpu_num(self):
        try:
            logic_cpu_num = -1
            with open('/proc/cpuinfo') as f:
                for line in f.readlines():
                    processor = re.findall(r'processor.*?(\d+)', line)
                    if processor:
                        logic_cpu_num = int(processor[0]) + 1
            if logic_cpu_num <= 0:
                raise Exception("get processor num failed")
            else:
                return logic_cpu_num
        except Exception as e:
            raise e

    def check_hw_state(self):
        """
        check hardware state
        such as cpu load, memory, disk ipos, network ipos
        """
        self.state = self.SLAVE_STATE_GREEN
        cpu, mem = self.get_hardware_overload()
        load_avg = self.get_load_average(minute=1)
        logic_cpu_num = self.get_logic_cpu_num()
        if cpu >= self.CPU_MAX_OVERLOAD or load_avg / logic_cpu_num >= 5:
            self.state = self.SLAVE_STATE_RED
        if mem >= self.MEM_MAX_OVERLOAD * self.get_total_mem():
            self.state = self.SLAVE_STATE_RED
